﻿#region

using System;
using System.IO;
using System.Reflection;
using System.Xml.Serialization;
using CommandLine;
using CommandLine.Text;
using Ionic.Zip;
using Ionic.Zlib;

#endregion

namespace BuildPluginInstall
{

    #region Enumerations

    public enum PluginType
    {
        file, // Plugins that provide file information, like Filename to Series translation, used in figuring out what a file is
        metadata, // Plugins that provide metadata info
        UI, // Plugins that provide UI services for the MediaCenter App
        maintenance, // Plugins that run on a regular basis to do larger tasks
        optimization, // Plugins that run more regularly to do optimization tasks
        metadatawriter // Plugins that export metadata from media other programs to read (Example: dvd_id.xml)
    }

    public enum PluginIDObjectType
    {
        integer,
        str,
        file
    }

    #endregion Enumerations

    public class Program
    {
        #region Fields

        private static readonly HeadingInfo _headingInfo = new HeadingInfo("BuildPluginInstall", "0.9");

        private static string[] IncludedFiles;
        private static string PackageOutput;
        private static string PluginDLL;
        private static int priority = 100;
        private static PluginType? ThisPluginType;
        private static string updateURL;
        private static InstallXML.IDType pluginObjectType;

        #endregion Fields

        #region Methods

        private static void Main(string[] args)
        {
            Console.WriteLine("Creating Plugin Package:");
            Console.WriteLine(" - Checking CLI args");
            ProcessCLI(args);
            System.Threading.Thread.Sleep(500);
            Console.WriteLine(String.Format(" - Plugin Type: {0}",ThisPluginType.ToString()));

            Console.WriteLine(" - Testing package contents");
            if (!File.Exists(PluginDLL))
            {
                Console.WriteLine(PluginDLL + " not found, aborting");
                Environment.Exit(1);
            }
            if (IncludedFiles != null)
            {
                foreach (string file in IncludedFiles)
                {
                    if (!File.Exists(file))
                    {
                        Console.WriteLine(file + " not found, aborting");
                        Environment.Exit(1);
                    }
                }
            }

            Console.WriteLine(" - Getting plugin version info");
            var InstallationXML = new InstallXML();
            Assembly pluginAssembly = null;
            try
            {
                pluginAssembly = Assembly.LoadFrom(PluginDLL);
            }
            catch (Exception e)
            {
                Console.WriteLine("Exception loading plugin: " + e.Message);
                Environment.Exit(1);
            }
            Version version = pluginAssembly.GetName().Version;
            InstallationXML.MajorVersion = version.Major;
            InstallationXML.MinorVersion = version.Minor;
            InstallationXML.BuildNumber = version.Build;

            InstallationXML.Name = pluginAssembly.GetName().Name;
            InstallationXML.MainDLL = Path.GetFileName(PluginDLL);
            InstallationXML.Type = (PluginType) ThisPluginType;
            InstallationXML.UpdateURL = updateURL;
            InstallationXML.Priority = priority;
            InstallationXML.IdType = pluginObjectType;

            Console.WriteLine(" - Creating temporary workspace");
            // We need to create a temporary directory to work in
            string tempDir = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());

            try
            {
                Directory.CreateDirectory(tempDir);
            }
            catch (Exception e)
            {
                Console.WriteLine("Failed to create TempDir: " + tempDir);
                Console.WriteLine("Error: " + e);
                Environment.Exit(1);
            }
            try
            {
                Console.WriteLine(" - Writing INSTALLER.XML");
                var root = new XmlRootAttribute("root");
                var ser = new XmlSerializer(typeof (InstallXML), root);
                var sw = new StreamWriter(Path.Combine(tempDir, "INSTALLER.XML"));
                ser.Serialize(sw, InstallationXML);
                sw.Close();
            }
            catch (Exception e)
            {
                Console.WriteLine("Failed to serialize INSTALLER.XML");
                Console.WriteLine("Error: " + e.Message);
                Environment.Exit(1);
            }

            Console.WriteLine(" - Copying files");
            try
            {
                Console.WriteLine("     - TempDir: "+tempDir);
                Console.WriteLine("     - Copying "+Path.GetFileName(PluginDLL));
                File.Copy(PluginDLL, Path.Combine(tempDir, Path.GetFileName(PluginDLL)), true);
                if (IncludedFiles != null)
                {
                    foreach (string file in IncludedFiles)
                    {
                        Console.WriteLine("     - Copying "+file);
                        Console.WriteLine("         to "+tempDir+Path.GetFileName(file));
                        File.Copy(file, Path.Combine(tempDir, Path.GetFileName(file)),true);
                    }
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("Failed to copy files over");
                Console.WriteLine("Error: " + e.Message);
                Environment.Exit(1);
            }

            Console.WriteLine(" - Creating Installation Package");
            try
            {
                using (var zip = new ZipFile(Path.Combine(tempDir, Path.GetFileName(PackageOutput))))
                {
                    zip.CompressionLevel = CompressionLevel.BestCompression;
                    Console.WriteLine("     - Adding INSTALLER.XML");
                    zip.AddFile(Path.Combine(tempDir, "INSTALLER.XML"), "");
                    zip.AddFile(Path.Combine(tempDir, Path.GetFileName(PluginDLL)), "");
                    Console.WriteLine("     - Adding " + Path.GetFileName(PluginDLL));
                    if (IncludedFiles != null)
                    {
                        foreach (string file in IncludedFiles)
                        {
                            Console.WriteLine("     - Adding " + file);
                            zip.AddFile(Path.Combine(tempDir, Path.GetFileName(file)), "");
                        }
                    }
                    zip.Save();
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("Failed to create Installation Package");
                Console.WriteLine("BuildPluginInstall - ERROR: " + e.Message);
                Environment.Exit(1);
            }

            Console.WriteLine(" - Copying installation package to output directory");
            try
            {
                File.Copy(Path.Combine(tempDir, Path.GetFileName(PackageOutput)), PackageOutput, true);
            }
            catch (Exception e)
            {
                Console.WriteLine("Failed to copy installation package to output");
                Console.WriteLine("BuildPluginInstall - ERROR: " + e.Message);
                Environment.Exit(1);
            }

            Console.WriteLine(" - Cleaning up");
            try
            {
                Directory.Delete(tempDir, true);
            }
            catch (Exception e)
            {
                Console.WriteLine("Failed to clean up temp dir");
                Console.WriteLine("BuildPluginInstall - ERROR: " + e.Message);
                Environment.Exit(1);
            }
            Console.WriteLine("ALL DONE!");
        }

        private static void ProcessCLI(string[] args)
        {
            var options = new Options();
            var parser = new CommandLineParser(new CommandLineParserSettings(false, true, Console.Error));
            if (!parser.ParseArguments(args, options))
            {
                Environment.Exit(1);
            }

            PackageOutput = options.PackageName;
            PluginDLL = options.DLL;
            IncludedFiles = options.IncludedFiles;

            if (options.Priority > 0)
            {
                priority = options.Priority;
            }

            if (options.MetadataWriterPlugin)
            {
                ThisPluginType = PluginType.metadatawriter;
            }
            if (options.FilePlugin)
            {
                ThisPluginType = PluginType.file;
            }
            if (options.MaintPlugin)
            {
                ThisPluginType = PluginType.maintenance;
            }
            if (options.OptimizePlugin)
            {
                ThisPluginType = PluginType.optimization;
            }
            if (options.UIPlugin)
            {
                ThisPluginType = PluginType.UI;
            }
            if (options.MetadataPlugin)
            {
                ThisPluginType = PluginType.metadata;
            }
            if (ThisPluginType == null)
            {
                Console.WriteLine("BuildPluginInstall - ERROR: A plugin type is required");
                Environment.Exit(1);
            }

            switch (options.IDObjectType.ToUpper())
            {
                case "FILE":
                    pluginObjectType = (InstallXML.IDType) PluginIDObjectType.file;
                    break;
                case "INT":
                    pluginObjectType = (InstallXML.IDType) PluginIDObjectType.integer;
                    break;
                case "INTEGER":
                    pluginObjectType = (InstallXML.IDType) PluginIDObjectType.integer;
                    break;
                case "STR":
                    pluginObjectType = (InstallXML.IDType) PluginIDObjectType.str;
                    break;
                case "STRING":
                    pluginObjectType = (InstallXML.IDType) PluginIDObjectType.str;
                    break;
                default:
                    Console.WriteLine("BuildPluginInstall - ERROR: idType must be file, int, or str");
                    Environment.Exit(1);
                    break;
            }

            updateURL = options.UpdateURL;
        }

        #endregion Methods
    }

    internal sealed class Options : CommandLineOptionsBase
    {
        #region Properties

        [Option("d", "dll", Required = true, HelpText = "Plugin main DLL")]
        public string DLL { get; set; }

        [Option("m", "metadata", MutuallyExclusiveSet = "type",
            HelpText = "Define as a metadata plugin, a plugin type is required")]
        public bool MetadataPlugin { get; set; }

        [Option("w", "writer", MutuallyExclusiveSet = "type",
            HelpText = "Define as a metadata writer plugin, a plugin type is required")]
        public bool MetadataWriterPlugin { get; set; }

        [Option("f", "file", MutuallyExclusiveSet = "type",
            HelpText = "Define as a file plugin, a plugin type is required")]
        public bool FilePlugin { get; set; }

        [OptionArray("i", "include", HelpText = "Files to include with plugin")]
        public string[] IncludedFiles { get; set; }

        [Option("a", "maint", MutuallyExclusiveSet = "type",
            HelpText = "Define as a maintenance plugin, a plugin type is required")]
        public bool MaintPlugin { get; set; }

        [Option("o", "optimize", MutuallyExclusiveSet = "type",
            HelpText = "Define as a optimization plugin, a plugin type is required")]
        public bool OptimizePlugin { get; set; }

        [Option("p", "package", Required = true, HelpText = "Package output name")]
        public string PackageName { get; set; }

        [Option("+", "priortiy", HelpText = "Default priority")]
        public int Priority { get; set; }

        [Option("u", "ui", MutuallyExclusiveSet = "type", HelpText = "Define as a UI plugin, a plugin type is required")
        ]
        public bool UIPlugin { get; set; }

        [Option("r", "updateURL", HelpText = "URL for package auto-update")]
        public string UpdateURL { get; set; }
        
        [Option("t", "idType", Required = true,
            HelpText = "The type of the ID this plugin expects: int, str, file (Default str)")]
        public string IDObjectType { get; set; }

        #endregion Properties

        #region Methods

        [HelpOption]
        public string GetUsage()
        {
            var ht = new HelpText(new HeadingInfo("BuildPluginInstall", "0.9"));
            string errors = ht.RenderParsingErrorsText(this, 2);
            // indent with two spaces
            if (!string.IsNullOrEmpty(errors))
            {
                ht.AddPreOptionsLine(string.Concat(Environment.NewLine, "ERROR(S):"));
                ht.AddPreOptionsLine(errors);
            }
            ht.AddOptions(this);
            return ht;
        }

        #endregion Methods
    }
}